/*
* INSANE - Interactive Structural Analysis Environment
*
* Copyright (C) 2003-2005
* Universidade Federal de Minas Gerais
* Escola de Engenharia
* Departamento de Engenharia de Estruturas
* 
* Author's email :    insane@dees.ufmg.br
* Author's website :  http://www.dees.ufmg.br/insane
* 
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or any later version.
* 
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
* 
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
package br.ufmg.dees.insane.model.femModel.element;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Set;

import br.ufmg.dees.insane.analysisModel.AnalysisModel;
import br.ufmg.dees.insane.materialMedia.constitutiveModel.ConstitutiveModel;
import br.ufmg.dees.insane.materialMedia.degeneration.Degeneration;
import br.ufmg.dees.insane.model.femModel.elementNode.ElementNode;
import br.ufmg.dees.insane.model.femModel.node.Node;
import br.ufmg.dees.insane.model.femModel.problemDriver.ProblemDriver;
import br.ufmg.dees.insane.shape.Shape;
import br.ufmg.dees.insane.util.IMatrix;
import br.ufmg.dees.insane.util.INaturalCoords;
import br.ufmg.dees.insane.util.IVector;
import br.ufmg.dees.insane.util.pointValues.PointValues;


/**
 * A class representing an element.
 * @author  Fonseca, Flavio & Pitangueira, Roque
 * @version  1.0
 * @since  September 2003
 */

public abstract class Element implements java.io.Serializable {
	
    /**The label of this Element.*/
	protected String label;
    
	/**The incidence of this Element.*/
	protected ArrayList<ElementNode> incidence = new ArrayList<ElementNode>();
		
	/**The AnalysisModel of this Element.*/
	protected AnalysisModel analysisModel;
	
	/**The ConstitutiveModel of this Element.*/
	protected ConstitutiveModel constitutiveModel;
	
	/**The Shape of this Element.*/
	protected Shape Shape;
	
	/**The Problem drive of this Element.*/
	protected ProblemDriver problemDriver;
	
	/**The Degeneration list of this Element.*/
	protected ArrayList<Degeneration> degenerations = new ArrayList<Degeneration>();
	
	/** The values of this Element. */
	protected HashMap<String, Object> elementValues;
	
	/** 
	 * Returns the label of this Element.
	 * @return  The label of this Element.
	 * @uml.property  name="label"
	 */
	public String getLabel(){
		return (label);
	};
	
	/** 
	 * Sets the label of this Element.
	 * @param a  The label of this Element.
	 * @uml.property  name="label"
	 */
	public void setLabel(String a){
		label = a;
	};
	
	/** 
	 * Returns the AnalysisModel of this Element.
	 * @return  The AnalysisModel of this Element.
	 * @uml.property  name="analysisModel"
	 */
	public AnalysisModel getAnalysisModel() {
		return (this.analysisModel);
	};
	
	/** 
	 * Sets the AnalysisModel of this Element.
	 * @param a  The AnalysisModel of this Element.
	 * @uml.property  name="analysisModel"
	 */
	public void setAnalysisModel(AnalysisModel a) {
		this.analysisModel = a;
	};
	
	/** 
	 * Returns the constitutiveModel of this Element.
	 * @return  Returns the constitutiveModel.
	 * @uml.property  name="constitutiveModel"
	 */
	public ConstitutiveModel getConstitutiveModel() {
		return constitutiveModel;
	}
	
	/** 
	 * Sets the constitutiveModel of this Element.
	 * @param constitutiveModel  The constitutiveModel to set.
	 * @uml.property  name="constitutiveModel"
	 */
	public void setConstitutiveModel(ConstitutiveModel constitutiveModel) {
		this.constitutiveModel = constitutiveModel;
	}
	
	/** 
	 * Returns the Shape of this Element.
	 * @return  The Shape of this Element.
	 * @uml.property  name="shape"
	 */
	public Shape getShape() {
		return (Shape);
	};
	
	/** 
	 * Sets the Shape of this Element.
	 * @param a  The Shape of this Element.
	 * @uml.property  name="shape"
	 */
	public void setShape(Shape a) {
		Shape = a;
	};
	
	/** 
	 * Gets the problem driver of this element
	 * @return Returns the problem driver of this element. 
	 */
	public ProblemDriver getMyProblemDriver() {
	  return this.problemDriver;
	}
	
	/** 
	 * Sets the problem driver of this element
	 * @param myProblemDriver The ProblemDriver to set. 
	 */
	public void setMyProblemDriver(ProblemDriver myProblemDriver) {
	  this.problemDriver = myProblemDriver;
	}
		  	
    /** 
     * Returns the incidence of this Element.
	 * @return  The incidence of this Element.
	 * @uml.property  name="incidence"
	 */
	public ArrayList<ElementNode> getIncidence() {
		return (incidence);
	};
	
	/** 
	 * Sets the incidence of this Element.
	 * @param a  The incidence of this Element.
	 * @uml.property  name="incidence"
	 */
	public void setIncidence(ArrayList<ElementNode> a) {
		incidence = a;
	};
	
	/** 
	 * Returns the Node in the specified position of the incidence of this Element.
	 * @param a The position of the desired Node in the incidence.
	 * @return The Node in the specified position of the incidence of this Element.
	 */
	public ElementNode getNode(int a) {
		return ((ElementNode) incidence.get(a));
	};
	
	/** 
	 * Returns the Node with the specified label.
	 * @param a The label of the desired Node.
	 * @return The Node with the specified label.
	 */
	public ElementNode getNode(String a) {
		ElementNode node;
		ListIterator nodes = incidence.listIterator();
		while (nodes.hasNext()) {
			node = (ElementNode) nodes.next();
			if (node.getNode().getLabel() == a) {
				return (node);
			};
		};
		return (null);
	};
	
	/** 
	 * Sets the node to this Element.
	 * @param a A Node a in a specified position b.
	 * @param b The position of the Node in the incidence.
	 */
	public void setNode(ElementNode a, int b){
		if (incidence.size() != b) {
			incidence.remove(b);
			incidence.add(b, a);
		}
		else{
			incidence.add(a);
		}
	}
	
	/** 
	 * Adds the specified Node to the incidence.
	 * @param a The Node to be added to the incidence.
	 */
	public void addNode(Node a) {
		incidence.add(new ElementNode(a));
	};
	
	/** 
	 * Returns the degeneration list of this Element.
	 * @return  The degeneration list of this Element.
	 * @uml.property  name="degenerations"
	 */
	public ArrayList<Degeneration> getDegenerations() {
		return (degenerations);
	}
	
	/** 
	 * Returns the degeneration with the specified label.
	 * @param label The specified label.
	 * @return The degeneration with the specified label.
	 */
	public Degeneration getDegeneration(String label) {
	    ListIterator degenerations = this.getDegenerations().listIterator();
	    while (degenerations.hasNext()) {
		    Degeneration dg = (Degeneration) degenerations.next();
		    if (dg.getLabel().equals(label)) {
		        return dg;
		    }
		}
	    return null;
	}
	
    /** 
     * Add a degeneration to this Element(cloned from model).
     * @param a The Degeneration to clone.
     */
    public abstract void addDegeneration(Degeneration dg);
    
	/** Init Degenerations of this Element.*/
	public abstract void initDegenerations();


	
	/** 
	 * The method return a IVector with the extrapolated values.
	 * @param a The natural coords of a set of points. 
	 * @param d The values of a set of points.
	 * @return values The extrapolated values to the element nodes.
	 */
	public abstract IVector extrapolatesToNodes(ArrayList a,ArrayList d);
	
	/** 
	 * Updates all variables of this element based on the nodal variables state.
	 */
	public abstract void update();
	
	/** 
	 * Inits this element.
	 */
	public void init(){
		this.initDegenerations();
		ListIterator dgs = this.degenerations.listIterator();
		while(dgs.hasNext()){
			Degeneration dgn = (Degeneration)dgs.next();
			dgn.init();
		}
		this.setDOFLabels();
		
	}
	
	/** 
	 * Returns the matrix A of this Element in the global coords.
	 * @return The matrix A of this Element in the global coords.
	 */
	public IMatrix getA(){
		return this.problemDriver.getA(this);
	}
	
	/** 
	 * Returns the matrix B of this Element in the global coords.
	 * @return The matrix B of this Element in the global coords.
	 */
	public IMatrix getB(){
		return this.problemDriver.getB(this);
	}
	
	/** 
	 * Returns the matrix C of this Element in the global coords.
	 * @return The matrix C of this Element in the global coords.
	 */
	public IMatrix getC(){
		return this.problemDriver.getC(this);
	}
	
	/** 
	 * Returns the vector E of this Element in the global coords.
	 * @return The vector E of this Element in the global coords.
	 */
	public IVector getE(){
		return this.problemDriver.getE(this);
	}
	
	/** 
	 * Returns the vector F of this Element in the global coords.
	 * @return The vector F of this Element in the global coords.
	 */
	public IVector getF(){
		return this.problemDriver.getF(this);
	}
	
	/** 
	 * Returns the incremental form of matrix A of this Element in the global coords.
	 * @return The incremental form of matrix A of this Element in the global coords.
	 */
	public IMatrix getIncrementalA( ) {
		return this.problemDriver.getIncrementalA(this);
	}
	
	/** 
	 * Returns the incremental form of matrix B of this Element in the global coords.
	 * @return The incremental form of matrix B of this Element in the global coords.
	 */
	public IMatrix getIncrementalB( ) {
		return this.problemDriver.getIncrementalB(this);
	}
	
	/** 
	 * Returns the incremental form of matrix C of this Element in the global coords.
	 * @return The incremental form of matrix C of this Element in the global coords.
	 */
	public IMatrix getIncrementalC( ) {
		return this.problemDriver.getIncrementalC(this);
	}
	
	/** 
	 * Returns the total form of matrix A of this Element in the global coords.
	 * @return The total form of matrix A of this Element in the global coords.
	 */
	public IMatrix getTotalA( ) {
		return this.problemDriver.getTotalA(this);
	}
	
	/** 
	 * Returns the total form of matrix B of this Element in the global coords.
	 * @return The total form of matrix B of this Element in the global coords.
	 */
	public IMatrix getTotalB( ) {
		return this.problemDriver.getTotalB(this);
	}
	
	/** 
	 * Returns the total form of matrix C of this Element in the global coords.
	 * @return The total form of matrix C of this Element in the global coords.
	 */
	public IMatrix getTotalC( ) {
		return this.problemDriver.getTotalC(this);
	}
	
	/** 
	 * Return the number of degress of freedom of this Element.
	 * @return The number of degrees of freedom.
	 */
	public int getNumberOfDegreesOfFreedom() {
		ListIterator nds = this.getIncidence().listIterator();
		int ndf = 0;
		while (nds.hasNext()) 
		{
			Node node = (Node) nds.next();
			ndf += node.getNumberOfDegreesOfFreedom();
		}
		return ndf;
	}
	
	/** 
	 * Returns the cartesian nodal coordinates matrix of this Element
	 * @return The cartesian nodal coordinates matrix.
	 */
	 public IMatrix getCartesianNodalCoordsMatrix(){
			IMatrix cN = new IMatrix(this.getIncidence().size(),this.getAnalysisModel().getDominion());
			ListIterator nds = this.getIncidence().listIterator();
			int position = 0;
			while (nds.hasNext()) 
			{
				Node node = (Node) nds.next();
				for (int i = 0; i < this.getAnalysisModel().getDominion(); i++) {
				    cN.setElement((position),i,node.getCoords()[i]);
	            }	
				position++;
			}
				return(cN);
		}
	
	/** 
	 * Returns the cartesian nodal coordinates vector of this Element
	 * @return The cartesian nodal coordinates vector.
	 */
	public IVector getCartesianNodalCoordsVector(){
		IVector elmNodeCoords = new IVector(this.getAnalysisModel().getDominion()*this.getIncidence().size());
		ListIterator nodes = incidence.listIterator();
		int j = 0;
		while(nodes.hasNext()) {
			ElementNode node = (ElementNode) nodes.next();
			for(int i=0; i<this.getAnalysisModel().getDominion(); i++) {
				elmNodeCoords.setElement(i+j,node.getCoords()[i]);
			}
			j = (j + analysisModel.getDominion());
		}
		return(elmNodeCoords);
	}
	
	/**
	 * Return the element integration coefficient
	 * @return thickness * (1 or 1/2) for plane elements and (1 or 1/6) for solid elements. 
	 * For bar elements returns the area of transversal section.
	 */
	public abstract double getElmCoefficient();
	
	private void setDOFLabels() {
		ArrayList nodalDOFlabels = this.getAnalysisModel().getDOFLabels(incidence.size());
		ListIterator dofLabels = incidence.listIterator();
		int i = 0;
        while (dofLabels.hasNext()) {
        	dofLabels.next();
        	this.getNode(i).init((PointValues)nodalDOFlabels.get(i));
        	i++;
        }
	}
	
	
	/** 
	 * Return the IVector containing the cartesian coordinates of a point.
	 * @param point The natural coordinates of the point.
	 * @return The IVector containing the cartesian coordinates of a point.
	 */
	public IVector getCartesianPointCoords(double[] point) {
		return null;
	}
	
	/** 
	 * Return the Internal Variables in a provided point.
	 * @param a The array containing the three coordinates of point.
	 * @return The IVector containing the InternalVariables in a provided point
	 */
	public IVector getPointInternalVariables(Degeneration dg) {
		double[] a = dg.getRepresentation().getNaturalcoords().toDouble();
		IVector displ = new IVector(this.getStateVariables());
		IMatrix T = analysisModel.getTransformationMatrix(Shape.getDerivedShapeFunction(a,this.getCartesianNodalCoordsMatrix()),this.getCartesianNodalCoordsMatrix()); 
		IMatrix matrixB = analysisModel.getInternalVariableOperator(Shape.getDerivedShapeFunction(a,this.getCartesianNodalCoordsMatrix()), Shape.getSecondDerivedShapeFunction(a,this.getCartesianNodalCoordsMatrix()), Shape.getShapeFunction(a,this.getCartesianNodalCoordsMatrix()), this.getCartesianNodalCoordsMatrix());
		matrixB = dg.internalVariableOperatorModifier(matrixB);
		return (matrixB.mul(T.mul(displ)));
	}	
	
	/**
	 * Return the nodal state variable of this Element.
	 */
	public double[] getStateVariables() {
		double[] elmNodeStateVariables = new double[(this.getNumberOfDegreesOfFreedom())];
		ListIterator nodes = incidence.listIterator();
		int j = 0;
		while(nodes.hasNext()) {
			int k = 0;
			ElementNode node = (ElementNode) nodes.next();
			PointValues pv = node.getNodeValues(node.DOF_LABELS);
			for(int i=0; i<pv.getSize(); i++) {
				elmNodeStateVariables[k+j] = node.getNode().getNodeValues(Node.STATE_VARIABLE).getDoublePointValue(i);
				k++;
			}
			j = (j + pv.getSize());
		}
		return(elmNodeStateVariables);
	}
	
	/** 
	 * Return the Internal Variables in a provided point.
	 * @param a The array containing the tree natural coordinates of the point.
	 * @return iv The IVector containing the internal variables in a provided point
	 */
	public IVector getPointInternalVariables(double[] a) {
		IVector displ = new IVector(this.getStateVariables());
		IMatrix T = analysisModel.getTransformationMatrix(Shape.getDerivedShapeFunction(a,this.getCartesianNodalCoordsMatrix()),this.getCartesianNodalCoordsMatrix()); 
		IMatrix matrixB = analysisModel.getInternalVariableOperator(Shape.getDerivedShapeFunction(a,this.getCartesianNodalCoordsMatrix()), Shape.getSecondDerivedShapeFunction(a,this.getCartesianNodalCoordsMatrix()), Shape.getShapeFunction(a,this.getCartesianNodalCoordsMatrix()), this.getCartesianNodalCoordsMatrix());
		matrixB = ((Degeneration)this.degenerations.get(0)).internalVariableOperatorModifier(matrixB);
		return (matrixB.mul(T.mul(displ)));
	}
	
	/** 
	 * Return the Stresses in a provided point.
	 * @param a The array containing the tree natural coordinates of point.
	 * @return sigma The IVector containing the dual internal variables in a provided point
	 */
	public IVector getPointDualInternalVariables(double[] a) {
//		return (((Degeneration)degenerations.get(0)).mountCs().mul(this.getPointInternalVariables(a)));
		return (((Degeneration)degenerations.get(0)).mountDualInternalVariableVector(this.getPointInternalVariables(((a)))));
	}
	
	/** 
	 * Return the IMatrix containing the Internal Variables in all element nodes.
	 * This method places all Internal Variables at each node in one column of output matrix.
     * @return nodalInternalVar The Internal Variables in all element nodes.
	 */
	public IMatrix getNodalPointInternalVariables(){
		IMatrix natCoords = Shape.getNaturalNodalCoords();
		IMatrix nodalInternalVar = new IMatrix(analysisModel.getNumberOfInternalVariables(), natCoords.getNumCol());
		double[]  aNodeNatCoord = new double[natCoords.getNumRow()];
		for(int i = 0; i < natCoords.getNumCol(); i++)//to nodal points
		{
			for(int j = 0; j < natCoords.getNumRow(); j++)//to natural coords. (xi, eta, zeta)
			{
				aNodeNatCoord[j] = natCoords.getElement(j,i);
			}
			nodalInternalVar.setColumn(i,this.getPointInternalVariables(aNodeNatCoord));
		}
			return nodalInternalVar;
	}
	
	/** 
	 * Return the IMatrix containing the Internal Variables in all element nodes.
	 * This method places all Internal Variables at each node in one column of output matrix.
     * @return nodalInternalVar The Internal Variables in all element nodes.
	 */	
	public IMatrix getNodalPointDualInternalVariables(){
		IMatrix natCoords = Shape.getNaturalNodalCoords();
		IMatrix nodalDualInternalVar = new IMatrix(analysisModel.getNumberOfDualInternalVariables(), natCoords.getNumCol());
		double[]  aNodeNatCoord = new double[natCoords.getNumRow()];
		for(int i = 0; i < natCoords.getNumCol(); i++)//este for e para percorrer todos os pontos nodais
		{
			for(int j = 0; j < natCoords.getNumRow(); j++)//este for e para percorrer as coordeandas xi, eta, zeta.
			{
				aNodeNatCoord[j] = natCoords.getElement(j,i);
			}
			nodalDualInternalVar.setColumn(i,this.getPointDualInternalVariables(aNodeNatCoord));
					
		}
		return nodalDualInternalVar;
	}
	
	/** 
	 * Return the IMatrix containing the Extrapolated Internal Variables in all element nodes.
	 * This method places all Extrapolated Internal Variables at each node in one column of output matrix.
     * @return nodalInternalVar The Extrapolated Internal Variables in all element nodes.
	 */
	public IMatrix getExtrapolatedNodalPointInternalVariables(){
		ArrayList<INaturalCoords> coords = new ArrayList<INaturalCoords>();
		IMatrix allValues = new IMatrix(this.analysisModel.getNumberOfInternalVariables(),this.degenerations.size());
		for (int i = 0; i < this.degenerations.size(); i++) {
			Degeneration dg = ((Degeneration)this.degenerations.get(i));
			coords.add(dg.getRepresentation().getNaturalcoords());
			IVector iv = this.getPointInternalVariables(dg);
			for (int j = 0; j < iv.getSize(); j++) {
				allValues.setElement(j,i,iv.getElement(j));
			}
		}
		IMatrix values = new IMatrix(this.analysisModel.getNumberOfInternalVariables(),this.incidence.size());
		for (int i = 0; i < allValues.getNumRow(); i++) {
			ArrayList a = allValues.getRow(i);
			IVector extrapolated = extrapolatesToNodes(coords,a);
			values.setRow(i,extrapolated);
		}
		return values;
	}
	
	/** 
	 * Return the IMatrix containing the Extrapolated Internal Variables in all element nodes.
	 * This method places all Extrapolated Internal Variables at each node in one column of output matrix.
     * @return nodalInternalVar The Extrapolated Internal Variables in all element nodes.
	 */
	public IMatrix getExtrapolatedNodalPointDualInternalVariables(){
		ArrayList<INaturalCoords> coords = new ArrayList<INaturalCoords>();
		IMatrix allValues = new IMatrix(this.analysisModel.getNumberOfInternalVariables(),this.degenerations.size());
		for (int i = 0; i < this.degenerations.size(); i++) {
			Degeneration dg = ((Degeneration)this.degenerations.get(i));
			coords.add(dg.getRepresentation().getNaturalcoords());
			IVector iv = this.getPointDualInternalVariables(dg.getRepresentation().getNaturalcoords().toDouble());
			for (int j = 0; j < iv.getSize(); j++) {
				allValues.setElement(j,i,iv.getElement(j));
			}
		}
		IMatrix values = new IMatrix(this.analysisModel.getNumberOfInternalVariables(),this.incidence.size());
		for (int i = 0; i < allValues.getNumRow(); i++) {
			ArrayList a = allValues.getRow(i);
			IVector extrapolated = extrapolatesToNodes(coords,a);
			values.setRow(i,extrapolated);
		}
		return values;
	}
	
	/** 
	 * Return the Internal Variables in a provided degeneration.
	 * @param dg The degeneration.
	 * @return The IVector containing the InternalVariables in a provided degeneration
	 */
	public IVector getDegenerationInternalVariables(Degeneration dg) {
		return this.getPointInternalVariables(dg);
	}
	
	/** 
	 * Return the Internal Variables in a provided material point of a degeneration.
	 * @param dg The degeneration.
	 * @param mplabel The material point label.
	 * @return The IVector containing the InternalVariables in a provided material point of a degeneration.
	 */
	public IVector getMPInternalVariables(Degeneration dg, String mplabel) {
		return dg.getMPInternalVariables(mplabel,this.getDegenerationInternalVariables(dg));
	}
	
	/**
	 * Return the Dual Internal Variables in a provided material point of a degeneration.
	 * @param dg The degeneration.
	 * @param mplabel The material point label.
	 * @return sigma The IVector containing the DualInternalVariables in a provided material point of a degeneration.
	 */
	public IVector getMPDualInternalVariables(Degeneration dg, String mplabel) {
		Set keys = ((Node)this.incidence.get(0)).getNodeValues().keySet();
		Iterator iter = keys.iterator();
		while (iter.hasNext()) {
			//String key = (String) iter.next();
			iter.next();
		}
		
		return dg.getMPDualInternalVariables(mplabel,this.getMPInternalVariables(dg,mplabel));
		
	}
	
	/**
	 * The method returns the elementValues.
	 * @return  Returns The elementValues.
	 * @uml.property  name="elementValues"
	 */
	public HashMap<String, Object> getElementValues() {
		return elementValues;
	}

	/**
	 * The method set the field elementValues from the parameter elementValues.
	 * @param elementValues  The elementValues to set.
	 * @uml.property  name="elementValues"
	 */
	public void setElementValues(HashMap<String, Object> elementValues) {
		this.elementValues = elementValues;
	}
	
	/** Returns the element point values.
	 * @param key The key of the point values.
	 * @return The node point values.
	 */
	  public PointValues getElementValues(String key) {
	      return (PointValues)elementValues.get(key);
	  }
	  
//**************************************************************************************************	
}//end of this class

